/*
 * libroxsd - a portable, lightweight XSD validation library
 * Copyright (C) 2012  Emmanuel Deloget
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

#include <errno.h>

#include <roxml.h>
#include "roxsd.h"
#include "roxsd-debug.h"
#include "roxsd-error.h"
#include "roxsd-compiler.h"
#include "roxsd-compiler-schema.h"

/*
 * Private internal functions
 */
static int roxsdi_has_xsd_namespace(node_t *node)
{
	node_t *ns;
	char *content;

	ns = roxml_get_ns(node);
	if (ns) {
		content = roxml_get_content(ns, NULL, 0, NULL);
		if (content) {
			LOG("Check: ns %s == %s", content, "http://www.w3.org/2001/XMLSchema");
			if (!strcmp(content, "http://www.w3.org/2001/XMLSchema")) {
				roxml_release(content);
				return 1;
			}
			roxml_release(content);
		}
	}
	return 0;
}

static int roxsdi_check_node_name(node_t *node, const char *name)
{
	char *content;
	int cmpresult = 1;

	content = roxml_get_name(node, NULL, 0);
	if (content) {
		LOG("Check: root %s == %s", content, name);
		cmpresult = strcmp(content, name);
		roxml_release(content);
	}
	return (cmpresult == 0);
}

/*
 * Public internal functions
 */
int roxsdi_compile_schema(xsdenv_t *xsdenv, node_t *node)
{
	node_t *root;

	if (roxsdi_compiler_register_schema_grammar(xsdenv) < 0) {
		roxsdi_xsderr_add(&xsdenv->errors, ROXSD_ERRCODE_INTERNAL, "failed to register XSD grammar", NULL);
		errno = EINVAL;
		return -1;
	}

	if (!xsdenv->xsdcompiler) {
		roxsdi_xsderr_add(&xsdenv->errors, ROXSD_ERRCODE_INTERNAL, "no XSD compiler found", NULL);
		errno = EINVAL;
		return -1;
	}

	/*
	 * 1. check if the root node is well formed
	 * A well formed root node must have the name <schema>, it must
	 * define a namespace whose name is http://www.w3.org/2001/XMLSchema
	 * The namespace alias is generally xs, but it can be whatever we
	 * want, as long as all nodes are using this namespace.
	 * Anyway, we don't check that.
	 */
	root = roxml_get_root(node);
	if (!roxsdi_check_node_name(root, "schema") || !roxsdi_has_xsd_namespace(root)) {
		roxsdi_xsderr_add(&xsdenv->errors, ROXSD_ERRCODE_NOT_XSD, NULL, root);
		errno = EINVAL;
		return -1;
	}

	if (xsdenv->errors) {
		errno = EINVAL;
		return -1;
	}

	return 0;
}

/*
 * Compiler API
 */

int roxsdi_compiler_register_type(xsdcompiler_t *compiler, xsdtype_t *t)
{
	if (!t || !compiler)
		return -1;

	compiler->types = list_pushfront(compiler->types, t);

	return 0;
}

int roxsdi_compiler_register_root(xsdcompiler_t *compiler, char *ename, char *etype)
{
	xsdtype_t *elem;

	elem = roxsdi_element_new(ename, etype);
	if (elem) {
		compiler->roots = list_pushfront(compiler->roots, elem);
		return 0;
	}

	return -1;
}

xsdcompiler_t *roxsdi_compiler_new()
{
	xsdcompiler_t *compiler;

	compiler = malloc(sizeof(xsdcompiler_t));
	if (!compiler)
		return NULL;

	memset(compiler, 0, sizeof(xsdcompiler_t));

	return compiler;
}

void roxsdi_compiler_free(xsdcompiler_t *compiler)
{
	/* for each type */
	while (compiler->types) {
		xsdtype_t *t;

		t = compiler->types;
		compiler->types = list_next(compiler->types);

		roxsdi_xsdtype_free(t);
	}

	/* for each root element */
	while (compiler->roots) {
		xsdtype_t *t;

		t = compiler->roots;
		compiler->roots = list_next(compiler->roots);

		roxsdi_xsdtype_free(t);
	}

	free(compiler);
}

#if !defined(NDEBUG)
static int roxsdi_compiler_check_element(xsdcompiler_t *compiler, xsdtype_t *element);
static int roxsdi_compiler_check_type(xsdcompiler_t *compiler, xsdtype_t *type);

static int roxsdi_compiler_check_type(xsdcompiler_t *compiler, xsdtype_t *type)
{
	xsdtype_t *cur;
	int children_are_types = 0;
	int children_maybe_elms = 0;

	type->type |= ROXSDI_TYPE_CHECKED;

	switch (type->type & ROXSDI_TYPE_MASK) {
	case ROXSDI_TYPE_COMPLEXTYPE:
		children_are_types = 1;
		break;
	case ROXSDI_TYPE_SIMPLETYPE:
		break;
	case ROXSDI_TYPE_CHOICE:
	case ROXSDI_TYPE_SEQUENCE:
		children_maybe_elms = 1;
		break;
	default:
		LOG("Error: object %s is not a type (%d)", type->tname, type->type);
		return -1;
		break;
	}

	if (children_are_types) {
		xsdtype_t *cur = type->children;
		while (cur) {
			if (roxsdi_compiler_check_type(compiler, cur) < 0)
				return -1;
			cur = list_next(cur);
		}
	}

	if (children_maybe_elms) {
		xsdtype_t *cur = type->children;
		while (cur) {
			if (cur->type == ROXSDI_TYPE_ELEMENT) {
				if (roxsdi_compiler_check_element(compiler, cur) < 0)
					return -1;
			} else {
				if (roxsdi_compiler_check_type(compiler, cur) < 0)
					return -1;
			}
			cur = list_next(cur);
		}
	}

	return 0;
}

static int roxsdi_compiler_check_element(xsdcompiler_t *compiler, xsdtype_t *element)
{
	xsdtype_t *etype;

	if (element->type != ROXSDI_TYPE_ELEMENT) {
		LOG("Error: object %p is not an element", element);
		return -1;
	}

	if (!element->name) {
		LOG("Error: element %p has no name", element);
		return -1;
	}

	if (!element->tname) {
		LOG("Error: element %s is missing a type", element->name);
		return -1;
	}

	etype = roxsdi_xsdtype_find(compiler->types, element->tname);
	if (!etype) {
		LOG("Error: element %s uses type %s that doesn't exist", element->name, element->tname);
		return -1;
	}

	if (!(etype->type & ROXSDI_TYPE_CHECKED)) {
		return roxsdi_compiler_check_type(compiler, etype);
	}
	return 0;
}

int roxsdi_compiler_check(xsdcompiler_t *compiler)
{
	xsdtype_t *elem;
	int errcount = 0;

	if (!compiler)
		return -1;

	elem = compiler->roots;
	while (elem) {
		if (roxsdi_compiler_check_element(compiler, elem) < 0) {
			++errcount;
		}
		elem = list_next(elem);
	}

	return errcount;
}
#endif
